

soc_net_pf_pmd_pci_probe { // PCI 的 probe 函数，板卡级别初始化所有的PF，分配eth设备管理结构
	soc_net_pf_init_device
	rte_eth_dev_create {
		soc_net_pf_eth_dev_init
	}
	rte_eth_dev_allocated
	soc_net_pf_eth_dev_uninit
}	

soc_net_pf_pmd_pci_remove {
	soc_net_pf_eth_dev_uninit
	rte_eth_dev_pci_release
	soc_net_pf_uninit_device {
		soc_net_pf_free_queues // 是个virtqueue使用到的内存
		soc_net_pf_vtpci_reset
		rte_pci_unmap_device
	}
}

soc_net_pf_init_device { // 映射FPGA的SOC寄存器，初始化结构成员
	rte_pci_map_device {
		0x81000 // 参见 FPGA 表格
		0x80000 // 参见 FPGA 表格
	}
	soc_net_pf_vtpci_reset { // SOC 的 FPGA 硬件队列 reset
		rte_write32(queue_reset)
	}
	soc_net_pf_alloc_queues { // 分配并初始化一个卡级别上的所有队列
		soc_net_pf_init_queue 
		soc_net_pf_free_queues {// 通过这个函数的释放内容，可以看到每个队列的内存组成
			soc_net_pf_get_queue_type //得到队列的类型
			rte_free
			rte_memzone_free
		}
	}
}

soc_net_pf_init_queue { //初始化PF的queue_idx队列：初始化队列参数，分配相关内存（虚拟地址和物理地址），挂接vq
	soc_net_pf_get_queue_type
	soc_net_pf_vtpci_set_queue_size // 设定队列的qsize
	rte_zmalloc_socket
	vring_size // 取得 vring 各个部分的总大小
	rte_memzone_reserve_aligned
	soc_net_pf_init_vring { // 初始化vq中 vring 相关结构，计算并初始化virtqueue各部分地址，构建desc的连接关系
		vring_init_split // desc, avail, used 各部分地址初始化
		vring_desc_init_split // desc 表初始化（链接关系）
		virtqueue_disable_intr // 设置禁止中断标志
	}
	rte_memzone_reserve_aligned（sz_hdr_mz）
	rte_zmalloc_socket
	vring_desc_init_split // 设置desc表的链接关系
	soc_net_pf_vtpci_setup_queue //used, avail, desc的地址下发给SOC硬件 
}		

soc_net_pf_eth_dev_init { // 根据配置参数，进行 PF 级别设备初始化，并挂接各种回调函数
	/* 通过这个可以找到 DPDK 的框架函数 */
	soc_net_pf_eth_dev_ops {
		soc_net_pf_dev_configure{
			soc_net_pf_dev_cq_start
		}			
		soc_net_pf_dev_start { //启动PF设备：分配队列空间，完成BUF填充，发送初始KICK，挂接收发函数，更新状态为UP
			soc_net_pf_rx_queue_setup_finish {//接收队列需要提前填满空闲 buffer
				soc_net_pf_hw_queue_idx  // 局部收发队列ID，映射全局硬件队列ID，参数queue_pair_idx是PF级的pair ID
				virtqueue_full // 判断 vq 是否满了
				rte_mbuf_raw_alloc
				virtqueue_enqueue_recv_refill // 用mbuf填充avail->ring，并更新vq->vq_avail_idx，一次可以refill一个链，但是初始化里只refill一个
					VIRTIO_MBUF_ADDR // 映射得到mbuf数据对应的iova
					vq_update_avail_ring //更新vq->vq_avail_idx和avail->ring[]指向的desc_id
				rte_pktmbuf_free
				vq_update_avail_idx // 更新avail->idx 为 vq->vq_avail_idx，这样可以在完全refill完队列后，一次一把更新，不用每个mbuf进行更新加1动作
				VIRTQUEUE_DUMP // 打印vq相关信息
			}
			soc_net_pf_tx_queue_setup_finish {// 发送队列不需要做额外的事情
				soc_net_pf_hw_queue_idx //局部收发队列ID，映射全局硬件队列ID
				VIRTQUEUE_DUMP
			}
			soc_net_pf_virtqueue_rxvq_flush{ //释放used ring 中原有的报文
				virtqueue_rxvq_flush_split // 释放used ring中的内容，并更新 vq_used_cons_idx （SW_LAST_USED_IDX）
					VIRTQUEUE_NUSED // 计算从上一次软件消费以来，有多少报文到达used ring
					rte_pktmbuf_free //释放 used ring (extra) 中的mbuf
					soc_net_pf_vq_ring_free_chain // 将释放后的desc放回到free chain中，追加到free_chain的最后，如果完全用完了，就追加到头上
			}
			virtqueue_notify { // 发出KICK
				soc_net_pf_notify_queue { //往SOC指定的地址发送KICK
					rte_write16(notify_addr)
				}
			}
			VIRTQUEUE_DUMP
			soc_net_pf_xmit_pkts {// 从tx_queue avail ring发送nb_pkts个报文，报文在tx_pkts中，发送到avail ring 里面
				VIRTQUEUE_NUSED
				virtio_xmit_cleanup{  // 从vq的used ring释放指定数量的描述符；【如果used ring超过free的阈值了，提前进行used ring的释放】
					soc_net_pf_vq_ring_free_chain
					rte_pktmbuf_free
				}
				/*
				 * 当VIRTIO_F_IN_ORDER协商成功之后，virtio驱动要按照ring的顺序使用描述符，从table 0开始并且在table 结束的位置做回滚。
				 * 当virtio device 添加available ring描述符时， 如果其中一个位置为x的描述符中VRING_DESC_F_NEXT被设置到flag上，描述符中next 的值必须为x+1 或者是0（如果x是最末位）。
				 * 如果是使用INDIRECT 描述符时，indirect描述符队列中的索引数值也需要连续，从0开始然后按顺序1，2，以此类推。
				 * 作为对应的实现，18.08中virtio user pmd中加入了两个新的收发函数：virtio_recv_mergeable_pkts_inorder和virtio_xmit_pkts_inorder。
				 * 为了配置in order和Rx_mrg这个对virtio pmd Rx/Tx path选择上非常重要的feature, 加入了两个新的virtio vdev参数。
				 * 一个是mrg_rxbuf 用来配置virtio user device支持VIRTIO_NET_F_MRG_RXBUF feature，默认配置是打开；
				 * 还有一个是in_order 用来配置VIRTIO_F_IN_ORDERfeature，默认配置也是打开。
				 * 所以现在如果在dpdk生成一个virtio 虚拟设备，它的默认参数会是“queues=1，queue_size=256，server=0,mrg_rxbuf=1，in_order=1”。

					head_idx = vq->vq_desc_head_idx;
					idx = head_idx;
					if (in_order) 
						dxp = &vq->vq_descx[vq->vq_avail_idx & (vq->vq_nentries - 1)]; //in_order直接使用vq_avail_idx，也就是avail ring 的下一个
					else
						dxp = &vq->vq_descx[idx]; // 不是inorder的，那么需要从free_chain的head开始使用，不能直接使用下一个
					
				 *
				 */
				virtqueue_enqueue_xmit // 将一个mbuf的内容，填充到avail ring里面，并更新index
					VIRTIO_MBUF_DATA_DMA_ADDR // 将mbuf的VA转换为物理地址iova, 并填写到 avail->ring[]的addr中
						VIRTIO_MBUF_ADDR
					vq_update_avail_ring  //更新vq->vq_avail_idx(加1)和avail->ring[]指向的desc_id
				soc_net_pf_update_packet_stats { //根据mbuf更新统计数据
					rte_pktmbuf_mtod
					rte_is_multicast_ether_addr
					rte_is_broadcast_ether_addr
				}
				vq_update_avail_idx //更新avail->idx
				virtqueue_notify // 所有的buffer发送完，才kick一次
			}
			soc_net_pf_recv_pkts { //从rx_queue的used ring里收取nb_pkts个报文到rx_pkts中
				VIRTQUEUE_NUSED
				virtqueue_dequeue_burst_rx {// 从vq的used ring里面出队num个报文到rx_pkts中，每个报文长度记录在len中
					rte_prefetch0 //恰当地使用rte_prefetch0(),可以减少cache-miss次数
					rte_pktmbuf_mtod 
					rte_packet_prefetch
					soc_net_pf_vq_ring_free_chain //把desc_idx开始的desc链释放返回给vq的free_chain					
				}
				virtio_discard_rxbuf //释放错误的mbuf回vq，以便下次继续使用
					virtqueue_enqueue_recv_refill //把1个要释放的mbuf放回到vq中
					rte_pktmbuf_free
				soc_net_pf_rx_stats_updated {// 更新mbuf到vq的RX队列统计
					VIRTIO_DUMP_PACKET
						rte_pktmbuf_dump 打印报文数据
					soc_net_pf_update_packet_stats //更新收队列的统计信息
				}
				virtqueue_full // 指导 rx 队列又满了
				rte_pktmbuf_alloc_bulk // 从 mpool 里，再分出 free_cnt 个 mbuf，到 new_pkts 里
				virtqueue_enqueue_recv_refill // 用新分配的 new_pkts 填充 vq
				rte_pktmbuf_free
				vq_update_avail_idx // 更新 avail->idx
				virtqueue_kick_prepare // 判断是否发送KICK，如VRING_USED_F_NO_NOTIFY
				virtqueue_notify //发送KICK
			}	
				
			soc_net_pf_dev_link_update { // 设定网卡状态为 UP
				rte_eth_linkstatus_set
			}
		}
		soc_net_pf_dev_stop { // 设定link状态为down
			rte_spinlock_lock
			rte_eth_linkstatus_set
		}
		soc_net_pf_dev_close { //清楚mac
			/*FIXME: 这里可能有为，需要和start完全对应起来*/
		}
		soc_net_pf_dev_mtu_set // nothing
		soc_net_pf_dev_info_get // 获取驱动信息
		soc_net_pf_dev_stats_get { // 收集统计信息
			soc_net_pf_update_stats
		}
		soc_net_pf_dev_xstats_get //统计每个队列的xstat
		soc_net_pf_dev_xstats_get_names // 队列的xstat名称
		soc_net_pf_dev_stats_reset //清空每个队列的统计信息 
		soc_net_pf_dev_link_update // 设定网卡状态
		soc_net_pf_rx_queue_setup { // 初始化卡级别的全局队列结构vq，并将这个vq挂载到PF级别的结构上和eth_dev的rx_queues上
			soc_net_pf_global_queue_idx // 得到卡级全局硬件队列ID，从本PF的queue_pair映射到卡级别的全局硬件队列ID
			soc_net_pf_hw_queue_idx //得到本PF级别的硬件队列ID，从本PF的queue_pair映射到本PF级别的硬件队列ID			
		}
		soc_net_pf_dev_queue_release // nothing
		soc_net_pf_tx_queue_setup{ // 初始化卡级别的全局队列结构vq，并将这个vq挂载到PF级别的结构上和eth_dev的tx_queues上, 并初始化空闲阈值
			soc_net_pf_global_queue_idx
			soc_net_pf_hw_queue_idx			
		}
		soc_net_pf_dev_queue_stats_mapping_set //nothing
	}
	rte_eth_random_addr
	rte_ether_addr_copy
}

soc_net_pf_eth_dev_uninit {
	soc_net_pf_dev_stop{
		rte_eth_linkstatus_set
	}
	soc_net_pf_dev_close
}

/*--------------------------------------------------------- 下面是模拟器实现 -----------------------------------------------------------------------------------------------*/	


main {
	sigint_handler --- SIGINT 
	rte_eal_init
	us_vhost_parse_args{ 
		getopt_long
		parse_num_opt
		us_vhost_usage
		us_vhost_parse_socket_path
	}
	TAILQ_INIT 
	rte_lcore_is_enabled
	rte_lcore_count 
	port_init {
		init_client {
			create_unix_stream_socket {
				socket
				set_unix_socket_path
				connect
			}			
		}
	}
	rte_ctrl_thread_create {
		print_stats
	}
	rte_eal_remote_launch {
		switch_worker {？
			TAILQ_FOREACH {
				drain_virtio_tx { // VIRTIO_TXQ 中的内容，刷新到 SOC (0) 收队列 vring 的 used ring 中
					vs_dequeue_pkts	{// 把VHOST的queue_idx对应的vq的avail内容，拷贝到soc vring的used ring中 （先拷贝到 soc vring 的 avail 中，在通过shadow刷新到 soc vring 的 used 中）
						get_device
						is_valid_virt_queue_idx // TODO: 校验奇偶数，可以暂时去掉
						rte_spinlock_trylock
						vhost_user_iotlb_rd_lock
						vring_translate //因为IOMMU导致的，要翻译iova_to_vva
						virtio_dev_tx_split {  //int queue_idx = vhost_dev->vid * 32 + 0; 不太理解，不是64个queue pair么？ 是因为host TX 要对应SOC的RX（偶数 0）么? 这里的32是什么意思？
												// 发送 TX 里面的数据包，发送到 SOC_QUEUE和 SOC_RING里面
												//把vq（TX）的count个avail内容，复制到soc_vr的avail的内容中，拷贝后，刷新到soc_vr的used ring里面
							fill_vec_buf_split { //把vq->avail->ring[avail_idx]的指向id的desc链的内容映射到buf_vec中，一共使用vec_idx个vector，映射的desc链起始ID保存在desc_chain_head中，长度保存在desc_chain_len中
								vhost_iova_to_vva
								vhost_alloc_copy_ind_table
								free_ind_table
								map_one_desc { // 把desc_iova长度为desc_len的描述符，映射到buf_vec中，一共使用vec_idx个buf_vec
									vhost_iova_to_vva //将guest物理地址(gpa)转换为host虚拟地址(vva)的过程
								}
								
							}
							update_shadow_used_ring_split // 更新本次映射的avail desc的ID到shadow中
														  // 更新 shadow_used_split[shadow_used_idx] 为 ring[last_avail_idx + 1]所存的 desc_id, 也就是后端这次消耗的avail的IDX
														  // shadow 里面记录了 消耗到avail 的 desc_id是多少 
							soc_reserve_avail_buf_split { // 从soc_vr中保留size长度的desc到buf_vec中，且供使用了nr_vec个vector；avail_head是soc_vring的avail起始位置，num_buffers对应了使用shadow的数量（只有错误回滚的时候使用），同时soc_queue的avail ring也用了num_buffers个
								soc_fill_vec_buf_split //从soc_vr中预留avail的desc到buf_vec，VECTOR数量保存到vec_idx中，总长度在desc_chain_len中，起始desc的ID保存在desc_chain_head中(这个值只用来更新shadow desc)
								soc_update_shadow_used_ring_split //更新soc_queue的shadow_used_split[shadow_used_idx]为要使用的soc desc的idx，也就是  soc_vr->avail->ring[soc_queue->last_avail_idx]
							}
							copy_buf_vector_host_2_soc { //把src_buf_vec的内容复制到dst_buf_vec中
								rte_memcpy
							}
							flush_shadow_used_ring_split { // 刷新 vq->shadow_used_split 中的 desc ID 们刷新到vq->used->ring[] 中，表示使用完成
								do_flush_shadow_used_ring_split { // 把 vq->shadow_used_split 中的 vring_used_elem 们刷新到vq->used->ring[] 中，表示使用完成
									rte_memcpy
								}
							}
							vhost_vring_call_split // 下面我们再看下后端vhost_user是如果kick前端的。首先是split ring的情况。向前端发送EVENT
							soc_flush_shadow_used_ring_split // 刷新 soc_queue->shadow_used_split 中的 vring_used_elem 们到soc_vr->used->ring[] 中，soc_vr->avail的数据刷新到了soc_vr->used里面
						}
						vhost_user_iotlb_rd_unlock
						rte_spinlock_unlock						
					}
				}
				drain_virtio_rx { ？
					vs_enqueue_pkts {？
						get_device 
						is_valid_virt_queue_idx
						rte_spinlock_lock
						vhost_user_iotlb_rd_lock
						vring_translate
						virtio_dev_rx_split {? // int queue_idx = vhost_dev->vid * 32 + 1; 不太理解，不是64个queue pair么？ 是因为host RX 要对应SOC的TX（偶数 1）么? 这里的32是什么意思？
							soc_fill_vec_buf_split
							soc_update_shadow_used_ring_split
							reserve_avail_buf_split { //从 vq->avail ring 里面avail_head 开始的desc 保留 size 长度的内容，保留的desc放到buf_vec中，使用nr_vec个
								rxvq_is_mergeable
								fill_vec_buf_split { //把vq->avail ring[avail_idx]的指向id的desc链的内容映射到buf_vec中，一共使用vec_idx个buf_vec，映射的desc链起始ID保存在desc_chain_head中，长度保存在desc_chain_len中
									vhost_iova_to_vva
									vhost_alloc_copy_ind_table
									map_one_desc {// 把desc_iova长度为desc_len的描述符，映射到buf_vec中，一共使用vec_idx个buf_vec
										vhost_iova_to_vva
									}
								}
								update_shadow_used_ring_split
							}
							copy_buf_vector_soc_2_host 
							soc_flush_shadow_used_ring_split
							flush_shadow_used_ring_split
							vhost_vring_call_split
						}
						vhost_user_iotlb_rd_unlock
						rte_spinlock_unlock
					}
				}
			}
		}
	}
	rte_vhost_driver_register // 注册 vhost的服务，VM和VHOST-USER连接？
	unregister_drivers
	rte_vhost_driver_disable_features
	rte_vhost_driver_callback_register { 
		virtio_net_device_ops {
			new_device {
				rte_zmalloc
				TAILQ_INSERT_TAIL
				rte_vhost_enable_guest_notification
			}
			destroy_device {
				rte_pause
				rte_free
			}
		}
	}
	RTE_LCORE_FOREACH_SLAVE {
		rte_eal_wait_lcore
	}	
}	


sigint_handler {
	unregister_drivers {
		rte_vhost_driver_unregister 
	}
}
	